package org.groovymud.object.registry;

import groovy.util.GroovyScriptEngine;
import groovy.util.ResourceException;
import groovy.util.ScriptException;
import org.apache.log4j.Logger;
import org.codehaus.groovy.control.CompilationFailedException;
import org.groovymud.object.Container;
import org.groovymud.object.MudObject;
import org.groovymud.object.ObjectLocation;
import org.groovymud.object.alive.Player;
import org.groovymud.object.registry.persistence.DataPersistenceException;
import org.groovymud.object.registry.persistence.MudDataPersistence;
import org.groovymud.scripting.domain.DomainManager;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.List;

/* Copyright 2008 Matthew Corby-Eaglen
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0 
 *
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 * See the License for the specific language governing permissions and 
 * limitations under the License. 
 */

/**
 * The MudObjectAttendant is responsible for loading, saving, registering and
 * configuring all mud objects in the game.
 * <p/>
 * The object can have an id
 */
public class MudObjectAttendant {

    private static final String PLAYER_STORE = new String("playerfiles" + File.separator);

    private final ObjectLocation theVoid;
    private final ObjectLocation playerImpl;

    private static final Logger logger = Logger.getLogger(MudObjectAttendant.class);

    private final transient MudDataPersistence<MudObject> persistence;

    private final transient Registry registry;
    private transient List<DomainManager<MudObject>> domainManagers;

    public MudObjectAttendant(ObjectLocation theVoid, ObjectLocation playerImpl, MudDataPersistence<MudObject> persistence, GroovyScriptEngine groovyScriptEngine, Registry registry) {
        this.theVoid = theVoid;
        this.playerImpl = playerImpl;
        this.persistence = persistence;
        this.registry = registry;
    }

    public MudObject findOrClone(ObjectLocation loc) throws MudCloneException {
        MudObject obj = registry.getMudObject(loc.getHandle());
        if (obj == null) {
            loadDomain(loc);
            obj = cloneObject(loc.getHandle());
        }
        return obj;
    }

    public MudObject cloneObject(String handle) throws MudCloneException {
        MudObject o = registry.getMudObject(handle);
        if (o == null) {
            o = loadObject(handle);
        }
        o = persistence.cloneObject(o);
        registry.register(o);
        return o;
    }

    /**
     * loads a mudobject object and initialise it using a handle
     * <p/>
     * Each domain manager is checked for an instance of the required handle
     * <p/>
     * an instance of that object is loaded from the manager, which may or may not
     * be a single instance
     *
     * @param handle - representing the name object
     * @throws MudCloneException
     */
    public MudObject loadObject(String handle) throws MudCloneException {
        MudObject obj = null;
        for (DomainManager<MudObject> domainManager : domainManagers) {

            if (domainManager.contains(handle)) {
                obj = domainManager.getObject(handle);
                break;
            }
        }
        return obj;
    }

    public void loadDomain(ObjectLocation location) {
        for (DomainManager<MudObject> domainManager : domainManagers) {
            if (domainManager.contains(location.getHandle())) {
                break;
            } else if (domainManager.canLoad(location.getDefinition())) {
                logger.info("loading definition" + location.getDefinition());
                domainManager.loadDomain(location.getDefinition());
            }
        }

    }
    public void loadPreLoadedDomains(){
        for (DomainManager<MudObject> domainManager : domainManagers) {
            domainManager.preLoadDomains();
        }
    }
    public Player loadPlayerData(String username) throws DataPersistenceException {
        logger.info("loading "+username+"'s object..");
        Player player;
        synchronized (this.getClass()) {
            loadDomain(getPlayerImpl());
            player = (Player) persistence.loadObject(PLAYER_STORE + username);
        }
        return player;
    }

    public void moveToLocation(MudObject player, ObjectLocation location) throws InstantiationException, FileNotFoundException, CompilationFailedException, MudCloneException {
        logger.info("moving player to location.");
        MudObject mudObject = registry.getMudObject(location.getHandle());
        if(mudObject == null){
            loadDomain(location);
            mudObject = loadObject(location.getHandle());
            registry.register(mudObject);
        }

        Container room = castToContainer(mudObject);

        if (room == null) {
            throw new InstantiationException("container was null!");
        }
        logger.info("adding player to container");
        room.addMudObject(player);
    }

    protected Container castToContainer(MudObject obj) {
        return (Container) obj;
    }

    public void movePlayerToVoid(Player player) throws IOException, FileNotFoundException {
        try {
            logger.info("moving player to the void..");
            moveToLocation(player, getTheVoid());
        } catch (CompilationFailedException e) {
            logger.error(e, e);
        } catch (InstantiationException e) {
            logger.error(e, e);
        } catch (MudCloneException e) {
            logger.error(e, e);
        }
    }

    public Player cloneAndSetPlayerDetails(String username) throws MudCloneException {
        logger.info("creating new player");
        MudObject obj = loadObject(playerImpl.getHandle());
        Player player = (Player) persistence.cloneObject(obj);
        String upperName = username.substring(0, 1).toUpperCase() + username.substring(1);

        player.setName(upperName);
        player.addShortName(upperName);
        player.addShortName(username);
        return player;
    }

    public void savePlayerData(Player player) {
        logger.info("saving player..");

        try {
            if (player.getPlayerCredentials() != null) {
                persistence.saveObject(PLAYER_STORE + player.getPlayerCredentials().getUsername(), player);
            } else {
                player.getTerminalOutput().writeln("Cannot save guests, sorry.");
            }

        } catch (Exception e) {
            try {
                player.getTerminalOutput().writeln(e.getMessage());
            } catch (IOException e1) {
                logger.error(e1, e1);
            }

            logger.error(e, e);
        }
    }

    public ObjectLocation getTheVoid() {
        return theVoid;
    }

    public ObjectLocation getPlayerImpl() {
        return playerImpl;
    }


    public void setDomainManagers(List<DomainManager<MudObject>> domainManagers) {
        this.domainManagers = domainManagers;
    }

    public MudDataPersistence<MudObject> getPersistence() {
        return persistence;
    }
}
